#include "Wave.h"



#pragma pack(push, 4)

typedef struct
{
	char			szRIFF[4];
	unsigned long	ulRIFFSize;
	char			szWAVE[4];
} WAVEFILEHEADER;

typedef struct
{
	char			szChunkName[4];
	unsigned long	ulChunkSize;
} RIFFCHUNK;

typedef struct
{
	unsigned short	usFormatTag;
	unsigned short	usChannels;
	unsigned long	ulSamplesPerSec;
	unsigned long	ulAvgBytesPerSec;
	unsigned short	usBlockAlign;
	unsigned short	usBitsPerSample;
	unsigned short	usSize;
	unsigned short  usReserved;
	unsigned long	ulChannelMask;
    GUID            guidSubFormat;
} WAVEFMT;

#pragma pack(pop)



ALboolean InitializeOpenAL()
{
	ALCcontext		*pContext = NULL;
	ALCdevice		*pDevice  = NULL;

	// Open the default device
	pDevice = alcOpenDevice(NULL);
	if(pDevice){
		pContext = alcCreateContext(pDevice, NULL);
		alcMakeContextCurrent(pContext);
		return AL_TRUE;
	}

	return AL_FALSE;
}

ALboolean Wave::Load(const char *ccWaveFile)
{
	// Generate an AL Buffer
	alGenBuffers(1, &uiBuffer);


	
	LoadWaveFile(ccWaveFile);

	ALint			iDataSize;
	GetWaveSize( (unsigned long*) &iDataSize);

	ALint			iFrequency;
	GetWaveFrequency((unsigned long*) &iFrequency);

	ALenum			eBufferFormat;
	GetWaveALBufferFormat(&alGetEnumValue, (unsigned long *) &eBufferFormat);

	ALchar			*pData;
	GetWaveData((void **)&pData);

	alGetError();
	alBufferData(uiBuffer, eBufferFormat, pData, iDataSize, iFrequency);
	
	if( alGetError() == AL_NO_ERROR)
	{
		// Generate a Source to playback the Buffer
		alGenSources(1, &uiSource);

		// Attach Source to Buffer
		alSourcei( uiSource, AL_BUFFER, uiBuffer);
		fVol = 1.0f;
		alSourcef(uiSource, AL_GAIN, fVol);
		return AL_TRUE;
	}

	if (m_WaveID->pData)
		delete m_WaveID->pData;
	if (m_WaveID->pFile)
		fclose(m_WaveID->pFile);
	delete m_WaveID;
	m_WaveID = 0;

	return AL_FALSE;
}

WAVERESULT Wave::Play()
{
	// Play Source
	alSourcePlay(uiSource);
	return WR_OK;
}

WAVERESULT Wave::Loop()
{
	alSourcei(uiSource, AL_LOOPING, AL_TRUE); 
	alSourcePlay(uiSource);
	return WR_OK;
}

WAVERESULT Wave::Volume(ALfloat val)
{
	
	if(fVol > 0.0f && fVol < 1.0f) 
		fVol += val;
	alSourcef(uiSource, AL_GAIN, fVol);
	printf("Volume: %f\n", fVol);
	return WR_OK;
}


WAVERESULT Wave::Stop()
{
	alSourceStop(uiSource);
	return WR_OK;
}

WAVERESULT Wave::Pause()
{
	alSourcePause(uiSource);
	return WR_OK;
}
WAVERESULT Wave::GetWaveData(void **lplpAudioData)
{
	if (!lplpAudioData)
		return WR_INVALIDPARAM;

	*lplpAudioData = m_WaveID->pData;

	return WR_OK;
}


WAVERESULT Wave::GetWaveALBufferFormat(PFNALGETENUMVALUE pfnGetEnumValue, unsigned long *pulFormat)
{
	WAVERESULT wr = WR_OK;

		if (pfnGetEnumValue && pulFormat)
		{
			*pulFormat = 0;

			if (m_WaveID->wfType == WF_EX)
			{
				if (m_WaveID->wfEXT.Format.nChannels == 1)
				{
					switch (m_WaveID->wfEXT.Format.wBitsPerSample)
					{
					case 4:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_MONO_IMA4");
						break;
					case 8:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_MONO8");
						break;
					case 16:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_MONO16");
						break;
					}
				}
				else if (m_WaveID->wfEXT.Format.nChannels == 2)
				{
					switch (m_WaveID->wfEXT.Format.wBitsPerSample)
					{
					case 4:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_STEREO_IMA4");
						break;
					case 8:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_STEREO8");
						break;
					case 16:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_STEREO16");
						break;
					}
				}
				else if ((m_WaveID->wfEXT.Format.nChannels == 4) && (m_WaveID->wfEXT.Format.wBitsPerSample == 16))
					*pulFormat = pfnGetEnumValue("AL_FORMAT_QUAD16");
			}
			else if (m_WaveID->wfType == WF_EXT)
			{
				if ((m_WaveID->wfEXT.Format.nChannels == 1) &&
					((m_WaveID->wfEXT.dwChannelMask == SPEAKER_FRONT_CENTER) ||
					 (m_WaveID->wfEXT.dwChannelMask == (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)) ||
					 (m_WaveID->wfEXT.dwChannelMask == 0)))
				{
					switch (m_WaveID->wfEXT.Format.wBitsPerSample)
					{
					case 4:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_MONO_IMA4");
						break;
					case 8:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_MONO8");
						break;
					case 16:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_MONO16");
						break;
					}
				}
				else if ((m_WaveID->wfEXT.Format.nChannels == 2) && (m_WaveID->wfEXT.dwChannelMask == (SPEAKER_FRONT_LEFT|SPEAKER_FRONT_RIGHT)))
				{
					switch (m_WaveID->wfEXT.Format.wBitsPerSample)
					{
					case 4:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_STEREO_IMA4");
						break;
					case 8:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_STEREO8");
						break;
					case 16:
						*pulFormat = pfnGetEnumValue("AL_FORMAT_STEREO16");
						break;
					}
				}
			}

			if (*pulFormat == 0)
				wr = WR_INVALIDWAVEFILETYPE;
		}
		else
		{
			wr = WR_INVALIDPARAM;
		}

	return wr;
}




WAVERESULT Wave::GetWaveFrequency(unsigned long *pulFrequency)
{
	WAVERESULT wr = WR_OK;

	if (pulFrequency)
		*pulFrequency = m_WaveID->wfEXT.Format.nSamplesPerSec;
	else
		wr = WR_INVALIDPARAM;

	return wr;
}



WAVERESULT Wave::GetWaveSize(unsigned long *size)
{
	if (!size)
		return WR_INVALIDPARAM;

	*size = m_WaveID->ulDataSize;

	return WR_OK;
}

WAVERESULT Wave::LoadWaveFile(const char *ccWaveFile)
{
	WAVERESULT			wr			=	WR_OUTOFMEMORY;
	LPWAVEFILEINFO		pWaveInfo;

	pWaveInfo = new WAVEFILEINFO;
	if( !pWaveInfo )
		return wr;

	wr = ParseFile(ccWaveFile, pWaveInfo);

	// Allocate memory for sample data
	pWaveInfo->pData = new char[pWaveInfo->ulDataSize];
	if( !pWaveInfo->pData )
	{
		wr = WR_OUTOFMEMORY;
		return wr;
	}

	// Seek to start of audio data
	fseek(pWaveInfo->pFile, pWaveInfo->ulDataOffset, SEEK_SET);

	// Read Sample Data
	if( fread(pWaveInfo->pData, 1, pWaveInfo->ulDataSize, pWaveInfo->pFile) == pWaveInfo->ulDataSize)
	{
		if( !m_WaveID)
		{
			m_WaveID = pWaveInfo;
			wr = WR_OK;
		}
	}
	else
	{
		delete pWaveInfo->pData;
		wr = WR_BADWAVEFILE;
	}

	fclose(pWaveInfo->pFile);
	pWaveInfo->pFile = 0;

	if( wr != WR_OK )
		delete pWaveInfo;

	return wr;

}


WAVERESULT Wave::ParseFile(const char *szFilename, LPWAVEFILEINFO pWaveInfo)
{
	WAVEFILEHEADER	waveFileHeader;
	RIFFCHUNK		riffChunk;
	WAVEFMT			waveFmt;
	WAVERESULT		wr = WR_BADWAVEFILE;

	if (!szFilename || !pWaveInfo)
		return WR_INVALIDPARAM;

	memset(pWaveInfo, 0, sizeof(WAVEFILEINFO));

	// Open the wave file for reading
	pWaveInfo->pFile = fopen(szFilename, "rb");
	if (pWaveInfo->pFile)
	{
		// Read Wave file header
		fread(&waveFileHeader, 1, sizeof(WAVEFILEHEADER), pWaveInfo->pFile);
		if (!strncmp(waveFileHeader.szRIFF, "RIFF", 4) && !strncmp(waveFileHeader.szWAVE, "WAVE", 4))
		{
			while (fread(&riffChunk, 1, sizeof(RIFFCHUNK), pWaveInfo->pFile) == sizeof(RIFFCHUNK))
			{
				if (!strncmp(riffChunk.szChunkName, "fmt ", 4))
				{
					if (riffChunk.ulChunkSize <= sizeof(WAVEFMT))
					{
						fread(&waveFmt, 1, riffChunk.ulChunkSize, pWaveInfo->pFile);
					
						// Determine if this is a WAVEFORMATEX or WAVEFORMATEXTENSIBLE wave file
						if (waveFmt.usFormatTag == WAVE_FORMAT_PCM)
						{
							pWaveInfo->wfType = WF_EX;
							memcpy(&pWaveInfo->wfEXT.Format, &waveFmt, sizeof(PCMWAVEFORMAT));
						}
						else if (waveFmt.usFormatTag == WAVE_FORMAT_EXTENSIBLE)
						{
							pWaveInfo->wfType = WF_EXT;
							memcpy(&pWaveInfo->wfEXT, &waveFmt, sizeof(WAVEFORMATEXTENSIBLE));
						}
					}
					else
					{
						fseek(pWaveInfo->pFile, riffChunk.ulChunkSize, SEEK_CUR);
					}
				}
				else if (!strncmp(riffChunk.szChunkName, "data", 4))
				{
					pWaveInfo->ulDataSize = riffChunk.ulChunkSize;
					pWaveInfo->ulDataOffset = ftell(pWaveInfo->pFile);
					fseek(pWaveInfo->pFile, riffChunk.ulChunkSize, SEEK_CUR);
				}
				else
				{
					fseek(pWaveInfo->pFile, riffChunk.ulChunkSize, SEEK_CUR);
				}

				// Ensure that we are correctly aligned for next chunk
				if (riffChunk.ulChunkSize & 1)
					fseek(pWaveInfo->pFile, 1, SEEK_CUR);
			}

			if (pWaveInfo->ulDataSize && pWaveInfo->ulDataOffset && ((pWaveInfo->wfType == WF_EX) || (pWaveInfo->wfType == WF_EXT)))
				wr = WR_OK;
			else
				fclose(pWaveInfo->pFile);
		}
	}
	else
	{
		wr = WR_INVALIDFILENAME;
	}

	return wr;
}

WAVERESULT Wave::DelWave()
{
	alDeleteSources(1, &uiSource);
	alDeleteBuffers(1, &uiBuffer);
	return WR_OK;
}

ALboolean ShutdownOpenAL()
{
	ALCcontext *pContext;
	ALCdevice *pDevice;

	pContext = alcGetCurrentContext();
	pDevice = alcGetContextsDevice(pContext);
	
	alcMakeContextCurrent(NULL);
	alcDestroyContext(pContext);
	alcCloseDevice(pDevice);

	return AL_TRUE;
}


